package com.hts.remote.invoke;



import com.alibaba.fastjson.JSONObject;
import com.hts.exception.CustomException;
import com.hts.remote.JcRemoteService;
import com.hts.remote.anno.JcServiceAnno;
import com.hts.remote.handler.ResponseHandler;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URI;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 远程服务调用
 * 以实现 方法 简单 get post
 * @author 洪松
 */
@Slf4j
public class JcRemoteInvokeMethod implements InvocationHandler {

    public RestTemplate restTemplate;
    public StandardEnvironment standardEnvironment;

    public String service;

    public ConcurrentHashMap<Method, RequestObject> requestMap;

    private HttpHeaders headers;

    private ResponseHandler responseHandler;

    public JcRemoteInvokeMethod(RestTemplate restTemplate, StandardEnvironment standardEnvironment, Class<? extends JcRemoteService> jcMoteServiceClass, ResponseHandler responseHandler) {
        this(restTemplate, standardEnvironment, jcMoteServiceClass);
        this.responseHandler = responseHandler;
    }

    public JcRemoteInvokeMethod(RestTemplate restTemplate, StandardEnvironment standardEnvironment, Class<? extends JcRemoteService> jcMoteServiceClass) {
        this.restTemplate = restTemplate;
        this.standardEnvironment = standardEnvironment;
        JcServiceAnno annotation = jcMoteServiceClass.getAnnotation(JcServiceAnno.class);
        if (annotation == null) {
            throw new CustomException("未找到服务");
        }
        String service = annotation.value();
        if (service.isEmpty()) {
            throw new CustomException("未找到服务");
        }
        if (service.startsWith("${")) {
            service = service.replace("${", "").replace("}", "");
            service = standardEnvironment.getProperty(service);
        }
        this.service = service;
        this.requestMap = new ConcurrentHashMap<>();
        Method[] declaredMethods = jcMoteServiceClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            RequestObject build = RequestObject.build(declaredMethod, service);
            requestMap.put(declaredMethod, build);
        }

        headers = new HttpHeaders();
        headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RequestObject requestObject = requestMap.get(method);
        if (Objects.isNull(requestObject)) {
            throw new CustomException("未找到对应的方法请求");
        }

        List<ParamObject> paramObjects = requestObject.getParamObjects();

        String methodName = requestObject.getMethodName();
        String url;
        if (methodName.startsWith("/")) {
            url = service + "/" + methodName;
        } else {
            url = service + "/" + methodName;
        }

        List<ParamObject> requestParam = paramObjects.stream().filter(paramObject -> paramObject.getType() == 0).toList();
        List<ParamObject> body = paramObjects.stream().filter(paramObject -> paramObject.getType() == 1).toList();
        Map<String, Object> params = getParams(args, requestParam);
        //TODO
        List<ParamObject> path = paramObjects.stream().filter(paramObject -> paramObject.getType() == 2).toList();

        HttpHeaders httpHeaders = requestObject.getHeaders();
        if (httpHeaders == null) {
            httpHeaders = this.headers;
        }

        HttpEntity<Object> requestEntity;
        URI uri = URI.create(url);
        if (Objects.equals(httpHeaders.getContentType(), MediaType.APPLICATION_FORM_URLENCODED)) {
            MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
            params.forEach(multiValueMap::add);
            requestEntity = new HttpEntity<>(multiValueMap, httpHeaders);
        } else {
            uri  = addParam(url, params);
            requestEntity = getHttpEntity(args, body, httpHeaders);
        }

        Class<?> returnType = method.getReturnType();
        log.info("httpMethod:{},uri:{},body:{}", requestObject.httpMethod, uri, body);

        if (this.responseHandler != null) {
            ResponseEntity<String> exchange = restTemplate.exchange(uri,
                    requestObject.httpMethod,
                    requestEntity,
                    String.class);
            return responseHandler.converStringToT(returnType, exchange.getBody());
        }
        return restTemplate.exchange(uri,
                requestObject.httpMethod,
                requestEntity,
                returnType).getBody();

    }

    private Map<String, Object> getParams(Object[] args, List<ParamObject> requestParam) {
        Map<String, Object> params = new HashMap<>();
        if (!requestParam.isEmpty()) {
            for (ParamObject paramObject : requestParam) {
                Object arg = args[paramObject.getIndex()];
                if (arg instanceof String || arg instanceof Number) {
                    params.put(paramObject.getName(), arg);
                } else if (arg instanceof Map) {
                    Map<String, ?> arg1 = (Map<String, ?>) arg;
                    params.putAll(arg1);
                } else if (arg instanceof Set || arg instanceof List) {
                    params.put(paramObject.getName(), arg);
                }
                //其他的对象处理
                else {
                    String jsonString = JSONObject.toJSONString(arg);
                    JSONObject jsonObject = JSONObject.parseObject(jsonString);
                    params.putAll(jsonObject);
                }
            }

        }
        return params;
    }

    private HttpEntity<Object> getHttpEntity(Object[] args, List<ParamObject> body, HttpHeaders httpHeaders) {
        HttpEntity<Object> requestEntity;
        if (!body.isEmpty()) {
            requestEntity = new HttpEntity<>(args[body.get(0).getIndex()], httpHeaders);
        } else {
            requestEntity = new HttpEntity<>(httpHeaders);
        }
        return requestEntity;
    }

    public static URI addParam(String url, Map<String, Object> params) {
        UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(url);
        for (Map.Entry<String, Object> stringObjectEntry : params.entrySet()) {
            uriComponentsBuilder.queryParam(stringObjectEntry.getKey(), stringObjectEntry.getValue());
        }
        return uriComponentsBuilder.build().encode().toUri();
    }

    @Data
    public static class RequestObject {
        /**
         * 服务名
         */
        private String service;
        /**
         * 请求method
         */
        private String methodName;

        /**
         * 请求方法
         */
        private RequestMethod requestMethod;

        private HttpMethod httpMethod;

        /**
         * 参数名
         */
        private List<ParamObject> paramObjects;

        private HttpHeaders headers;
        /**
         * 请求体
         */
        private Integer bodyIndex;

        /**
         * 构建请求对象
         *
         * @param method
         * @param service
         * @return
         */
        public static RequestObject build(Method method, String service) {
            RequestObject requestObject = new RequestObject();

            requestObject.service = service;
            RequestMapping requestMapping = AnnotatedElementUtils.getMergedAnnotation(method, RequestMapping.class);
            requestObject.requestMethod = RequestMethod.GET;
            requestObject.methodName = method.getName();
            if (Objects.nonNull(requestMapping)) {
                requestObject.requestMethod = requestMapping.method()[0];
                requestObject.httpMethod = HttpMethod.resolve(requestObject.requestMethod.name());

                String[] value = requestMapping.value();
                if (value.length > 0) {
                    requestObject.methodName = value[0];
                }
                String[] headersString = requestMapping.headers();
                HttpHeaders httpHeaders = new HttpHeaders();
                for (String s : headersString) {
                    String[] split = s.split("=");
                    httpHeaders.add(split[0], split[1]);
                }
                requestObject.headers = httpHeaders;
            }

            Parameter[] parameters = method.getParameters();
            if (parameters == null) {
                return requestObject;
            }
            int i = 0;
            for (Parameter parameter : parameters) {
                ParamObject paramObject = ParamObject.build(parameter, i);
                requestObject.addParmaObject(paramObject);
                i++;
            }

            return requestObject;
        }

        /**
         * 添加参数
         *
         * @param paramObject
         */
        public void addParmaObject(ParamObject paramObject) {
            if (this.getParamObjects() == null) {
                this.paramObjects = new ArrayList<>();
            }
            this.paramObjects.add(paramObject);

        }

    }

    @AllArgsConstructor
    @Data
    public static class ParamObject {
        /**
         * 0 参数
         * 1 请求体
         * 2 路径参数
         */
        private Integer type;

        private String name;

        private int index;

        public static ParamObject build(Parameter parameter, Integer index) {

            RequestBody requestBody = parameter.getAnnotation(RequestBody.class);

            if (Objects.nonNull(requestBody)) {
                return new ParamObject(1, parameter.getName(), index);
            }

            RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
            if (Objects.nonNull(requestParam)) {
                String value = requestParam.value();
                if (value.isEmpty()) {
                    throw new CustomException("@RequestParma is not value");
                }
                return new ParamObject(0, value, index);
            }

            PathVariable pathVariable = parameter.getAnnotation(PathVariable.class);
            if (Objects.nonNull(pathVariable)) {
                return new ParamObject(2, parameter.getName(), index);
            }

            return new ParamObject(0, parameter.getName(), index);
        }
    }


}
